package com.mongo.jdbc;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import com.mongodb.Bytes;








/**
 * 
 * @author:binggong
 * @createtime:2019年8月17日 下午5:47:58
 * @version:V.0.1
 * @tag: hbase jdbc PreparedStatement
 *
 */
public class PreparedStatement extends MongodbAbstractStatement implements
java.sql.PreparedStatement{
	
	
	private boolean[] isStream = null;
	
	protected boolean[] isNull = null;
	
	private InputStream[] parameterStreams = null;
	
	private byte[][] parameterValues = null;
	
	protected int parameterCount;
	
	private byte[][] staticSqlStrings = null;
	
	protected ParseInfo parseInfo;
	
	private String charEncoding ="UTF-8";
	
	protected int batchCommandIndex = -1;
	protected int[] parameterTypes = null;
	
	protected List<Object> batchedArgs;
	
	private com.mongo.jdbc.Connection conn ;
	private int[] streamLengths = null;
	public class BatchParams {
		public boolean[] isNull = null;

		public boolean[] isStream = null;

		public InputStream[] parameterStreams = null;

		public byte[][] parameterStrings = null;

		public int[] streamLengths = null;

		BatchParams(byte[][] strings, InputStream[] streams,
				boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) {
			//
			// Make copies
			//
			this.parameterStrings = new byte[strings.length][];
			this.parameterStreams = new InputStream[streams.length];
			this.isStream = new boolean[isStreamFlags.length];
			this.streamLengths = new int[lengths.length];
			this.isNull = new boolean[isNullFlags.length];
			System.arraycopy(strings, 0, this.parameterStrings, 0,
					strings.length);
			System.arraycopy(streams, 0, this.parameterStreams, 0,
					streams.length);
			System.arraycopy(isStreamFlags, 0, this.isStream, 0,
					isStreamFlags.length);
			System.arraycopy(lengths, 0, this.streamLengths, 0, lengths.length);
			System
					.arraycopy(isNullFlags, 0, this.isNull, 0,
							isNullFlags.length);
		}
	}
	
	public PreparedStatement(){
		
	}
	
	public PreparedStatement(com.mongo.jdbc.Connection conn,String sql ) {
		super(sql,conn);
		try {
			this.conn = conn;
			this.parseInfo = new ParseInfo(sql, conn, null,
					this.charEncoding,false);
		
			initializeFromParseInfo();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public static PreparedStatement getProxyInstance(com.mongo.jdbc.Connection conn,String sql){
		
		return new PreparedStatement(conn, sql);
		
	}
	
	public ResultSet executeQuery(String sql) throws SQLException {
		// TODO Auto-generated method stub
		return super.executeQuery(sql);
		//return this.resultSet;
	}

	public int executeUpdate(String sql) throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void close() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int getMaxFieldSize() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setMaxFieldSize(int max) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int getMaxRows() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setMaxRows(int max) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void setEscapeProcessing(boolean enable) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int getQueryTimeout() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setQueryTimeout(int seconds) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void cancel() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public SQLWarning getWarnings() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public void clearWarnings() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void setCursorName(String name) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public boolean execute(String sql) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public ResultSet getResultSet() throws SQLException {
		// TODO Auto-generated method stub
		return this.resultSet;
	}

	public int getUpdateCount() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean getMoreResults() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public void setFetchDirection(int direction) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int getFetchDirection() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setFetchSize(int rows) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int getFetchSize() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getResultSetConcurrency() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getResultSetType() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void addBatch(String sql) throws SQLException {
		// TODO Auto-generated method stub
//		synchronized (checkClosed().getConnectionMutex()) {
			if (this.batchedArgs == null) {
				this.batchedArgs = new ArrayList<Object>();
			}
	
			for (int i = 0; i < this.parameterValues.length; i++) {
				checkAllParametersSet(this.parameterValues[i],
						this.parameterStreams[i], i);
			}
			
			this.batchedArgs.add(new BatchParams(this.parameterValues,
					this.parameterStreams, this.isStream, this.streamLengths,
					this.isNull));
//		}
	}

	private void checkAllParametersSet(byte[] parameterString,
			InputStream parameterStream, int columnIndex) throws SQLException {
		if ((parameterString == null)
				&& parameterStream == null) {

			throw new SQLException("parameterString =null && parameterString = null ");
		}
	}
	public void clearBatch() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public int[] executeBatch() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public Connection getConnection() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public boolean getMoreResults(int current) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public ResultSet getGeneratedKeys() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public int executeUpdate(String sql, int autoGeneratedKeys)
			throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public int executeUpdate(String sql, int[] columnIndexes)
			throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public int executeUpdate(String sql, String[] columnNames)
			throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean execute(String sql, int autoGeneratedKeys)
			throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean execute(String sql, int[] columnIndexes) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean execute(String sql, String[] columnNames)
			throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public int getResultSetHoldability() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean isClosed() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public void setPoolable(boolean poolable) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public boolean isPoolable() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public void closeOnCompletion() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public boolean isCloseOnCompletion() throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public Object unwrap(Class iface) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public boolean isWrapperFor(Class iface) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

	public ResultSet executeQuery() throws SQLException {
		// TODO Auto-generated method stub
		String sql1 = asSql(false);
		return super.executeQuery(sql1);
		//return this.resultSet;
	}

	public int executeUpdate() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setNull(int parameterIndex, int sqlType) throws SQLException {
		// TODO Auto-generated method stub
	}

	public void setBoolean(int parameterIndex, boolean x) throws SQLException {
		// TODO Auto-generated method stub
		//this.setBytes(parameterIndex, String.valueOf(x).getBytes());
		//if ( x != null) {
		
			setInternal(parameterIndex,String.valueOf(x).getBytes());
			//this.setBytes(parameterIndex, x.getBytes());
//		} else {
//			this.setInternal(parameterIndex,null);
//		}
		this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BOOLEAN;

	}

	public void setByte(int parameterIndex, byte x) throws SQLException {
		// TODO Auto-generated method stub

	}

	public void setShort(int parameterIndex, short x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());

	}

	public void setInt(int parameterIndex, int x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());

	}

	public void setLong(int parameterIndex, long x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());


	}

	public void setFloat(int parameterIndex, float x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());

		System.out.println("setFloat (int parameterIndex, float x) ");

	}

	public void setDouble(int parameterIndex, double x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());

		System.out.println(" setDouble(int parameterIndex, double x) ");

	}

	public void setBigDecimal(int parameterIndex, BigDecimal x)
			throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());

		System.out.println("setBigDecimal(int parameterIndex, BigDecimal x) ");

	}

	public void setString(int parameterIndex, String x) throws SQLException {
		// TODO Auto-generated method stub
		
//		if ( x != null) {
//			x = x.replaceAll(" ", "0x00");
//			setInternal(parameterIndex,SafeEncoder.encode(x));
//			//this.setBytes(parameterIndex, x.getBytes());
//		} else {
		if (x != null ) {
			this.setInternal(parameterIndex,("'"+x+"'").getBytes());
		} else {
			this.setInternal(parameterIndex,null);
		}
		
//		}
		//System.out.println(" setString(int parameterIndex, String x) ");
	}

	public void setBytes(int parameterIndex, byte[] x) throws SQLException {
		// TODO Auto-generated method stub

		setBytes(parameterIndex, x, true, true);
		
		if (x != null) {
			this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BINARY;
		}
	}

	public void setDate(int parameterIndex, Date x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setDate(int parameterIndex, byte[] x)");

	}

	public void setTime(int parameterIndex, Time x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setTime(int parameterIndex, byte[] x)");

	}

	public void setTimestamp(int parameterIndex, Timestamp x)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setTimestamp(int parameterIndex, byte[] x)");

	}

	public void setAsciiStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setAsciiStream(int parameterIndex, InputStream x, int length)");

	}

	public void setUnicodeStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setUnicodeStream(int parameterIndex, InputStream x, int length)");

	}

	public void setBinaryStream(int parameterIndex, InputStream x, int length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setBinaryStream(int parameterIndex, InputStream x, int length)");

	}

	public void clearParameters() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void setObject(int parameterIndex, Object x, int targetSqlType)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setObject(int parameterIndex, InputStream x, int length)");

	}

	public void setObject(int parameterIndex, Object x) throws SQLException {
		// TODO Auto-generated method stub
		setInternal(parameterIndex,String.valueOf(x).getBytes());
		
	}

	public boolean execute() throws SQLException {
		// TODO Auto-generated method stub
		String sql1 = asSql(false);
		
		return super.execute(sql1);
	}

	public void addBatch() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void setCharacterStream(int parameterIndex, Reader reader, int length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setCharacterStream(int parameterIndex, Object x) ");

	}

	public void setRef(int parameterIndex, Ref x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setRef(int parameterIndex, Object x) ");

	}

	public void setBlob(int parameterIndex, Blob x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setBlob(int parameterIndex, Object x) ");

	}

	public void setClob(int parameterIndex, Clob x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println(" setClob(int parameterIndex, Object x) ");

	}

	public void setArray(int parameterIndex, Array x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setArray(int parameterIndex, Array x)");
	}

	public ResultSetMetaData getMetaData() throws SQLException {
		// TODO Auto-generated method stub
		return new MongodbResultSetMetaData();
	}

	public void setDate(int parameterIndex, Date x, Calendar cal)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setDate(int parameterIndex, Date x, Calendar cal)");

	}

	public void setTime(int parameterIndex, Time x, Calendar cal)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setTime(int parameterIndex, Time x, Calendar cal)");

	}

	public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setTimestamp(int parameterIndex, Timestamp x, Calendar cal)");

	}

	public void setNull(int parameterIndex, int sqlType, String typeName)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNull(int parameterIndex, int sqlType, String typeName)");

	}

	public void setURL(int parameterIndex, URL x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setURL(int parameterIndex, URL x)");

	}

	public ParameterMetaData getParameterMetaData() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public void setRowId(int parameterIndex, RowId x) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setRowId(int parameterIndex, RowId x) ");

	}

	public void setNString(int parameterIndex, String value)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNString(int parameterIndex, String value) ");

	}

	public void setNCharacterStream(int parameterIndex, Reader value,
			long length) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNCharacterStream(int parameterIndex, Reader value, ");

	}

	public void setNClob(int parameterIndex, NClob value) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNClob(int parameterIndex, NClob value) ");

	}

	public void setClob(int parameterIndex, Reader reader, long length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setClob(int parameterIndex, Reader reader, long length) ");
	}

	public void setBlob(int parameterIndex, InputStream inputStream, long length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setBlob3");

	}

	public void setNClob(int parameterIndex, Reader reader, long length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNClob2");
	}

	public void setSQLXML(int parameterIndex, SQLXML xmlObject)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setSQLXML");

	}

	public void setObject(int parameterIndex, Object x, int targetSqlType,
			int scaleOrLength) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setObject");

	}

	public void setAsciiStream(int parameterIndex, InputStream x, long length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setAsciiStream");
	}

	public void setBinaryStream(int parameterIndex, InputStream x, long length)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setBinaryStream");

	}

	public void setCharacterStream(int parameterIndex, Reader reader,
			long length) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setCharacterStream");
	}

	public void setAsciiStream(int parameterIndex, InputStream x)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setAsciiStream");
	}

	public void setBinaryStream(int parameterIndex, InputStream x)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setBinaryStream");

	}

	public void setCharacterStream(int parameterIndex, Reader reader)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setCharacterStream");
	}

	public void setNCharacterStream(int parameterIndex, Reader value)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNCharacterStream");

	}

	public void setClob(int parameterIndex, Reader reader) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setClob");
	}

	public void setBlob(int parameterIndex, InputStream inputStream)
			throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setBlob");

	}

	public void setNClob(int parameterIndex, Reader reader) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("setNClob");

	}
	
	
	protected void setBytes(int parameterIndex, byte[] x,
			boolean checkForIntroducer, boolean escapeForMBChars)
			throws SQLException {
		System.out.println("setBytes");

	//	synchronized (checkClosed().getConnectionMutex()) {
			if (x == null) {
				setNull(parameterIndex, java.sql.Types.BINARY);
			} else {
				String connectionEncoding = "UTF-8";
						//this.connection.getEncoding();
	
		
	
				// escape them
				int numBytes = x.length;
	
				int pad = 2;
	
//				boolean needsIntroducer = checkForIntroducer
//						&& this.connection.versionMeetsMinimum(4, 1, 0);
//	
//				if (needsIntroducer) {
//					pad += 7;
//				}
	
				ByteArrayOutputStream bOut = new ByteArrayOutputStream(numBytes
						+ pad);
	
//				if (needsIntroducer) {
//					bOut.write('_');
//					bOut.write('b');
//					bOut.write('i');
//					bOut.write('n');
//					bOut.write('a');
//					bOut.write('r');
//					bOut.write('y');
//				}
				bOut.write('\'');
	
				for (int i = 0; i < numBytes; ++i) {
					byte b = x[i];
	
					switch (b) {
					case 0: /* Must be escaped for 'mysql' */
						bOut.write('\\');
						bOut.write('0');
	
						break;
	
					case '\n': /* Must be escaped for logs */
						bOut.write('\\');
						bOut.write('n');
	
						break;
	
					case '\r':
						bOut.write('\\');
						bOut.write('r');
	
						break;
	
					case '\\':
						bOut.write('\\');
						bOut.write('\\');
	
						break;
	
					case '\'':
						bOut.write('\\');
						bOut.write('\'');
	
						break;
	
					case '"': /* Better safe than sorry */
						bOut.write('\\');
						bOut.write('"');
	
						break;
	
					case '\032': /* This gives problems on Win32 */
						bOut.write('\\');
						bOut.write('Z');
	
						break;
	
					default:
						bOut.write(b);
					}
				}
	
				bOut.write('\'');
	
				setInternal(parameterIndex, bOut.toByteArray());
			}
		//	}
		}
		
		protected final void setInternal(int paramIndex, byte[] val)
				throws SQLException {
			//synchronized (checkClosed().getConnectionMutex()) {
				int parameterIndexOffset = getParameterIndexOffset();
				
				//checkBounds(paramIndex, parameterIndexOffset);
				
				this.isStream[paramIndex - 1 + parameterIndexOffset] = false;
				this.isNull[paramIndex - 1 + parameterIndexOffset] = false;
				this.parameterStreams[paramIndex - 1 + parameterIndexOffset] = null;
				this.parameterValues[paramIndex - 1 + parameterIndexOffset] = val;

		//	}
		}
		
		protected int getParameterIndexOffset() {
			return 0;
		}
	
		private void initializeFromParseInfo() throws SQLException {
			///synchronized (checkClosed().getConnectionMutex()) {
				this.staticSqlStrings = this.parseInfo.staticSql;
//				this.hasLimitClause = this.parseInfo.foundLimitClause;
//				this.isLoadDataQuery = this.parseInfo.foundLoadData;
//				this.firstCharOfStmt = this.parseInfo.firstStmtChar;
//				
				this.parameterCount = this.staticSqlStrings.length - 1;
		
				this.parameterValues = new byte[this.parameterCount][];
				this.parameterStreams = new InputStream[this.parameterCount];
				this.isStream = new boolean[this.parameterCount];
				this.streamLengths = new int[this.parameterCount];
				this.isNull = new boolean[this.parameterCount];
				this.parameterTypes = new int[this.parameterCount];
			
				clearParameters();
		
				for (int j = 0; j < this.parameterCount; j++) {
					this.isStream[j] = false;
				}
				//	}
		}

		class ParseInfo {
			char firstStmtChar = 0;

			boolean foundLimitClause = false;

			boolean foundLoadData = false;

			long lastUsed = 0;

			int statementLength = 0;

			int statementStartPos = 0;

			boolean canRewriteAsMultiValueInsert = false;
			
			byte[][] staticSql = null;

			boolean isOnDuplicateKeyUpdate = false;
			
			int locationOfOnDuplicateKeyUpdate = -1;
			
			String valuesClause;
			
			boolean parametersInDuplicateKeyClause = false;
			
			/**
			 * Represents the "parsed" state of a client-side
			 * prepared statement, with the statement broken up into
			 * it's static and dynamic (where parameters are bound) 
			 * parts.
			 */
//			ParseInfo(String sql, MySQLConnection conn,
//					java.sql.DatabaseMetaData dbmd, String encoding,
//					SingleByteCharsetConverter converter) throws SQLException {
//				this(sql, conn, dbmd, encoding, converter, true);
//			}
			
			public ParseInfo(String sql, com.mongo.jdbc.Connection conn,
					java.sql.DatabaseMetaData dbmd, String encoding,
					 boolean buildRewriteInfo) throws SQLException {
				try {
//					if (sql == null) {
//						throw SQLError.createSQLException(Messages
//								.getString("PreparedStatement.61"), //$NON-NLS-1$
//								SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
//					}

//					this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql);
					this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1;
					
					this.lastUsed = System.currentTimeMillis();

					String quotedIdentifierString = null;
					//dbmd.getIdentifierQuoteString();

					char quotedIdentifierChar = 0;

					if ((quotedIdentifierString != null)
							&& !quotedIdentifierString.equals(" ") //$NON-NLS-1$
							&& (quotedIdentifierString.length() > 0)) {
						quotedIdentifierChar = quotedIdentifierString.charAt(0);
					}

					this.statementLength = sql.length();

					ArrayList<int[]> endpointList = new ArrayList<int[]>();
					boolean inQuotes = false;
					char quoteChar = 0;
					boolean inQuotedId = false;
					int lastParmEnd = 0;
					int i;

					int stopLookingForLimitClause = this.statementLength - 5;

					this.foundLimitClause = false;

		//			boolean noBackslashEscapes = connection.isNoBackslashEscapesSet();

					// we're not trying to be real pedantic here, but we'd like to 
					// skip comments at the beginning of statements, as frameworks
					// such as Hibernate use them to aid in debugging

					statementStartPos = findStartOfStatement(sql);

					for (i = statementStartPos; i < this.statementLength; ++i) {
						char c = sql.charAt(i);

						if ((this.firstStmtChar == 0) && Character.isLetter(c)) {
							// Determine what kind of statement we're doing (_S_elect,
							// _I_nsert, etc.)
							this.firstStmtChar = Character.toUpperCase(c);
						}

						if (
								c == '\\' && i < (this.statementLength - 1)) {
							i++;
							continue; // next character is escaped
						}

						// are we in a quoted identifier?
						// (only valid when the id is not inside a 'string')
						if (!inQuotes && (quotedIdentifierChar != 0)
								&& (c == quotedIdentifierChar)) {
							inQuotedId = !inQuotedId;
						} else if (!inQuotedId) {
							//	only respect quotes when not in a quoted identifier

							if (inQuotes) {
								if (((c == '\'') || (c == '"')) && c == quoteChar) {
									if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) {
										i++; 
										continue; // inline quote escape
									}

									inQuotes = !inQuotes;
									quoteChar = 0;
								} else if (((c == '\'') || (c == '"')) && c == quoteChar) {
									inQuotes = !inQuotes;
									quoteChar = 0;
								}
							} else {
								if (c == '#'
									|| (c == '-' && (i + 1) < this.statementLength && sql
											.charAt(i + 1) == '-')) {
									// run out to end of statement, or newline,
									// whichever comes first
									int endOfStmt = this.statementLength - 1;

									for (; i < endOfStmt; i++) {
										c = sql.charAt(i);

										if (c == '\r' || c == '\n') {
											break;
										}
									}

									continue;
								} else if (c == '/' && (i + 1) < this.statementLength) {
									// Comment?
									char cNext = sql.charAt(i + 1);

									if (cNext == '*') {
										i+= 2;

										for (int j = i; j < this.statementLength; j++) {
											i++;
											cNext = sql.charAt(j);

											if (cNext == '*' && (j + 1) < this.statementLength) {
												if (sql.charAt(j + 1) == '/') {
													i++;

													if (i < this.statementLength) {
														c = sql.charAt(i);
													}

													break; // comment done
												}
											}
										}
									}
								} else if ((c == '\'') || (c == '"')) {
									inQuotes = true;
									quoteChar = c;
								}
							}
						}

						if ((c == '?') && !inQuotes && !inQuotedId) {
							endpointList.add(new int[] { lastParmEnd, i });
							lastParmEnd = i + 1;
							
							if (isOnDuplicateKeyUpdate && i > locationOfOnDuplicateKeyUpdate) {
								parametersInDuplicateKeyClause = true;
							}
						}

						if (!inQuotes && !inQuotedId && (i < stopLookingForLimitClause)) {
							if ((c == 'L') || (c == 'l')) {
								char posI1 = sql.charAt(i + 1);

								if ((posI1 == 'I') || (posI1 == 'i')) {
									char posM = sql.charAt(i + 2);

									if ((posM == 'M') || (posM == 'm')) {
										char posI2 = sql.charAt(i + 3);

										if ((posI2 == 'I') || (posI2 == 'i')) {
											char posT = sql.charAt(i + 4);

											if ((posT == 'T') || (posT == 't')) {

												boolean hasPreviosIdChar = false;
												boolean hasFollowingIdChar = false;
												if (i>statementStartPos && StringUtils.isValidIdChar(sql.charAt(i - 1))) {
													hasPreviosIdChar = true;
												}
												if (i + 5 < this.statementLength && StringUtils.isValidIdChar(sql.charAt(i + 5))) {
													hasFollowingIdChar = true;
												}
												if (!hasPreviosIdChar && !hasFollowingIdChar) {
													foundLimitClause = true;
												}
											}
										}
									}
								}
							}
						}
					}

					if (this.firstStmtChar == 'L') {
						if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$
							this.foundLoadData = true;
						} else {
							this.foundLoadData = false;
						}
					} else {
						this.foundLoadData = false;
					}

					endpointList.add(new int[] { lastParmEnd, this.statementLength });
					this.staticSql = new byte[endpointList.size()][];
					char[] asCharArray = sql.toCharArray();

					for (i = 0; i < this.staticSql.length; i++) {
						int[] ep = endpointList.get(i);
						int end = ep[1];
						int begin = ep[0];
						int len = end - begin;

						if (this.foundLoadData) {
							String temp = new String(asCharArray, begin, len);
							this.staticSql[i] = StringUtils.getBytes(temp);
						} else if (encoding == null) {
							byte[] buf = new byte[len];

							for (int j = 0; j < len; j++) {
								buf[j] = (byte) sql.charAt(begin + j);
							}

							this.staticSql[i] = buf;
						} else {
//							if (converter != null) {
//								this.staticSql[i] = StringUtils.getBytes(sql,
//										converter, encoding, connection
//										.getServerCharacterEncoding(), begin,
//										len, connection.parserKnowsUnicode(), getExceptionInterceptor());
//							} else {
								String temp = new String(asCharArray, begin, len);

								this.staticSql[i] = StringUtils.getBytes(temp,
										encoding, null,
										false, conn, null);
//							}
						}
					}
				} catch (StringIndexOutOfBoundsException oobEx) {
					SQLException sqlEx = new SQLException("Parse error for " + sql);
					sqlEx.initCause(oobEx);

					throw sqlEx;
				}
				
				
//				if (buildRewriteInfo) {
//					this.canRewriteAsMultiValueInsert = PreparedStatement
//							.canRewrite(sql, this.isOnDuplicateKeyUpdate,
//									this.locationOfOnDuplicateKeyUpdate,
//									this.statementStartPos) && !this.parametersInDuplicateKeyClause;
//
//					if (this.canRewriteAsMultiValueInsert
//							&& conn.getRewriteBatchedStatements()) {
//						buildRewriteBatchedParams(sql, conn, dbmd, encoding,
//								converter);
//					}
//				}
			}

			private ParseInfo batchHead;

			private ParseInfo batchValues;

			private ParseInfo batchODKUClause;

//			private void buildRewriteBatchedParams(String sql, com.hbase.jdbc.Connection conn,
//					DatabaseMetaData metadata, String encoding,
//					Object converter) throws SQLException {
//				this.valuesClause = extractValuesClause(sql);
//				String odkuClause = isOnDuplicateKeyUpdate ? sql
//						.substring(locationOfOnDuplicateKeyUpdate) : null;
//
//				String headSql = null;
//
//				if (isOnDuplicateKeyUpdate) {
//					headSql = sql.substring(0, locationOfOnDuplicateKeyUpdate);
//				} else {
//					headSql = sql;
//				}
//
//				this.batchHead = new ParseInfo(headSql, conn, metadata, encoding,
//						converter, false);
//				this.batchValues = new ParseInfo("," + this.valuesClause, conn,
//						metadata, encoding, converter, false);
//				this.batchODKUClause = null;
//
//				if (odkuClause != null && odkuClause.length() > 0) {
//					this.batchODKUClause = new ParseInfo("," + this.valuesClause
//							+ " " + odkuClause, conn, metadata, encoding,
//							converter, false);
//				}
//			}

//			private String extractValuesClause(String sql) throws SQLException {
//				String quoteCharStr = connection.getMetaData()
//						.getIdentifierQuoteString();
//
//				int indexOfValues = -1;
//				int valuesSearchStart = statementStartPos;
//
//				while (indexOfValues == -1) {
//					if (quoteCharStr.length() > 0) {
//						indexOfValues = StringUtils.indexOfIgnoreCaseRespectQuotes(
//								valuesSearchStart,
//								originalSql, "VALUES", quoteCharStr.charAt(0), false);
//					} else {
//						indexOfValues = StringUtils.indexOfIgnoreCase(valuesSearchStart, 
//								originalSql,
//								"VALUES");
//					}
//					
//					if (indexOfValues > 0) {
//						/* check if the char immediately preceding VALUES may be part of the table name */
//						char c = originalSql.charAt(indexOfValues - 1);
//						if(!(Character.isWhitespace(c) || c == ')' || c == '`')){
//							valuesSearchStart = indexOfValues + 6;
//							indexOfValues = -1;
//						} else {
//							/* check if the char immediately following VALUES may be whitespace or open parenthesis */
//							c = originalSql.charAt(indexOfValues + 6);
//							if(!(Character.isWhitespace(c) || c == '(')){
//								valuesSearchStart = indexOfValues + 6;
//								indexOfValues = -1;
//							}
//						}
//					} else {
//						break;
//					}
//				}
//
//				if (indexOfValues == -1) {
//					return null;
//				}
//
//				int indexOfFirstParen = sql.indexOf('(', indexOfValues + 6);
//
//				if (indexOfFirstParen == -1) {
//					return null;
//				}
//
//				int endOfValuesClause = sql.lastIndexOf(')');
//
//				if (endOfValuesClause == -1) {
//					return null;
//				}
//
//				if (isOnDuplicateKeyUpdate) {
//					endOfValuesClause = this.locationOfOnDuplicateKeyUpdate - 1;
//				}
//
//				return sql.substring(indexOfFirstParen, endOfValuesClause + 1);
//			}

			/**
			 * Returns a ParseInfo for a multi-value INSERT for a batch of size numBatch (without parsing!).
			 */
//			synchronized ParseInfo getParseInfoForBatch(int numBatch) {
//			//	AppendingBatchVisitor apv = new AppendingBatchVisitor();
//				buildInfoForBatch(numBatch, apv);
//
//				ParseInfo batchParseInfo = new ParseInfo(apv.getStaticSqlStrings(),
//						this.firstStmtChar, this.foundLimitClause,
//						this.foundLoadData, this.isOnDuplicateKeyUpdate,
//						this.locationOfOnDuplicateKeyUpdate, this.statementLength,
//						this.statementStartPos);
//
//				return batchParseInfo;
//			}
			
			/**
			 * Returns a preparable SQL string for the number of batched parameters, used by server-side prepared statements
			 * when re-writing batch INSERTs.
			 */
//			
//			String getSqlForBatch(int numBatch) throws UnsupportedEncodingException {
//				ParseInfo batchInfo = getParseInfoForBatch(numBatch);
//				
//				return getSqlForBatch(batchInfo);
//			}
			
			/** 
			 * Used for filling in the SQL for getPreparedSql() - for debugging 
			 */
			String getSqlForBatch(ParseInfo batchInfo) throws UnsupportedEncodingException {
				int size = 0;
				final byte[][] sqlStrings = batchInfo.staticSql;
				final int sqlStringsLength = sqlStrings.length;
				
				for (int i = 0; i < sqlStringsLength; i++) {
					size += sqlStrings[i].length;
					size++; // for the '?'
				}
				
				StringBuffer buf = new StringBuffer(size);
			
				for (int i = 0; i < sqlStringsLength - 1; i++) {
					buf.append(StringUtils.toString(sqlStrings[i], "UTF-8"));
					buf.append("?");
				}
				
				buf.append(StringUtils.toString(sqlStrings[sqlStringsLength - 1]));
				
				return buf.toString();
			}

			/**
			 * Builds a ParseInfo for the given batch size, without parsing. We use
			 * a visitor pattern here, because the if {}s make computing a size for the
			 * resultant byte[][] make this too complex, and we don't necessarily want to 
			 * use a List for this, because the size can be dynamic, and thus we'll not be
			 * able to guess a good initial size for an array-based list, and it's not
			 * efficient to convert a LinkedList to an array.
			 */
//			private void buildInfoForBatch(int numBatch, BatchVisitor visitor) {
//				final byte[][] headStaticSql = this.batchHead.staticSql;
//				final int headStaticSqlLength = headStaticSql.length;
//
//				if (headStaticSqlLength > 1) {
//					for (int i = 0; i < headStaticSqlLength - 1; i++) {
//						visitor.append(headStaticSql[i]).increment();
//					}
//				}
//
//				// merge end of head, with beginning of a value clause
//				byte[] endOfHead = headStaticSql[headStaticSqlLength - 1];
//				final byte[][] valuesStaticSql = this.batchValues.staticSql;
//				byte[] beginOfValues = valuesStaticSql[0];
//
//				visitor.merge(endOfHead, beginOfValues).increment();
//
//				int numValueRepeats = numBatch - 1; // first one is in the "head"
//
//				if (this.batchODKUClause != null) {
//					numValueRepeats--; // Last one is in the ODKU clause
//				}
//
//				final int valuesStaticSqlLength = valuesStaticSql.length;
//				byte[] endOfValues = valuesStaticSql[valuesStaticSqlLength - 1];
//
//				for (int i = 0; i < numValueRepeats; i++) {
//					for (int j = 1; j < valuesStaticSqlLength - 1; j++) {
//						visitor.append(valuesStaticSql[j]).increment();
//					}
//					visitor.merge(endOfValues, beginOfValues).increment();
//				}
//
//				if (this.batchODKUClause != null) {
//					final byte[][] batchOdkuStaticSql = this.batchODKUClause.staticSql;
//					byte[] beginOfOdku = batchOdkuStaticSql[0];
//					visitor.decrement().merge(endOfValues, beginOfOdku).increment();
//
//					final int batchOdkuStaticSqlLength = batchOdkuStaticSql.length;
//					
//					if (numBatch > 1) {
//						for (int i = 1; i < batchOdkuStaticSqlLength; i++) {
//							visitor.append(batchOdkuStaticSql[i])
//									.increment();
//						}
//					} else {
//						visitor.decrement().append(batchOdkuStaticSql[(batchOdkuStaticSqlLength - 1)]);
//					}
//				} else {
//					// Everything after the values clause, but not ODKU, which today is nothing
//					// but a syntax error, but we should still not mangle the SQL!
//					visitor.decrement().append(this.staticSql[this.staticSql.length - 1]);
//				}
//			}

			private ParseInfo(byte[][] staticSql, char firstStmtChar,
					boolean foundLimitClause, boolean foundLoadData,
					boolean isOnDuplicateKeyUpdate,
					int locationOfOnDuplicateKeyUpdate, int statementLength,
					int statementStartPos) {
				this.firstStmtChar = firstStmtChar;
				this.foundLimitClause = foundLimitClause;
				this.foundLoadData = foundLoadData;
				this.isOnDuplicateKeyUpdate = isOnDuplicateKeyUpdate;
				this.locationOfOnDuplicateKeyUpdate = locationOfOnDuplicateKeyUpdate;
				this.statementLength = statementLength;
				this.statementStartPos = statementStartPos;
				this.staticSql = staticSql;
			}
		}
		
		protected int findStartOfStatement(String sql) {
			int statementStartPos = 0;

			if (StringUtils.startsWithIgnoreCaseAndWs(sql, "/*")) {
				statementStartPos = sql.indexOf("*/");

				if (statementStartPos == -1) {
					statementStartPos = 0;
				} else {
					statementStartPos += 2;
				}
			} else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "--")
				|| StringUtils.startsWithIgnoreCaseAndWs(sql, "#")) {
				statementStartPos = sql.indexOf('\n');

				if (statementStartPos == -1) {
					statementStartPos = sql.indexOf('\r');

					if (statementStartPos == -1) {
						statementStartPos = 0;
					}
				}
			}

			return statementStartPos;
		}
		
		
		protected String asSql(boolean quoteStreamsAndUnknowns) throws SQLException {
			//synchronized (checkClosed().getConnectionMutex()) {
				
				StringBuffer buf = new StringBuffer();
		
				try {
					int realParameterCount = this.parameterCount + getParameterIndexOffset();
					Object batchArg = null;
					if (batchCommandIndex != -1)
						batchArg = batchedArgs.get(batchCommandIndex);
					
					for (int i = 0; i < realParameterCount; ++i) {
						if (this.charEncoding != null) {
							buf.append(StringUtils.toString(this.staticSqlStrings[i],
									this.charEncoding));
						} else {
							buf.append(StringUtils.toString(this.staticSqlStrings[i]));
						}
		
						byte val[] = null;
						if (batchArg != null && batchArg instanceof String) {
							buf.append((String)batchArg);
							continue;
						}


						if (batchCommandIndex == -1)
							val = parameterValues[i];
						else
							val = ((BatchParams)batchArg).parameterStrings[i]; 
						
						boolean isStreamParam = false;
						if (batchCommandIndex == -1)
							isStreamParam = isStream[i];
//						else
//							isStreamParam = ((BatchParams)batchArg).isStream[i];
		
//						if ((val == null) && !isStreamParam) {
//							if (quoteStreamsAndUnknowns) {
//								buf.append("'");
//							}
//		
//							buf.append("** NOT SPECIFIED **"); //$NON-NLS-1$
//		
//							if (quoteStreamsAndUnknowns) {
//								buf.append("'");
//							}
//						} else if (isStreamParam) {
//							if (quoteStreamsAndUnknowns) {
//								buf.append("'");
//							}
//		
//							buf.append("** STREAM DATA **"); //$NON-NLS-1$
//		
//							if (quoteStreamsAndUnknowns) {
//								buf.append("'");
//							}
//						} else {
//							if (this.charConverter != null) {
//								buf.append(this.charConverter.toString(val));
//							} else {
								if (this.charEncoding != null) {
										//if( val != null ) {}
									
										buf.append(new String(val, this.charEncoding));
								
									
								} else {
									buf.append(StringUtils.toAsciiString(val));
								}
//							}
						}
//					}
		
					if (this.charEncoding != null) {
						buf.append(StringUtils.toString(
								this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()],
								this.charEncoding));
					} else {
						buf
								.append(StringUtils
										.toAsciiString(this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()]));
					}
				} catch (UnsupportedEncodingException uue) {
					throw new RuntimeException(uue); //$NON-NLS-1$
				}
		
				return buf.toString();
		//	}
		}
}
